import logging
from functools import partial
from collections import defaultdict
from clutch.proxy import Proxy, Manager, object_manager

log = logging.getLogger(__name__)

class LazyObjectManager(Manager):
    obj = None
    actualized = False
    lazy_methods = set([
        'init', 'call', 'getattr', 'setattr', 'repr', 'str',
        'iter', 'contains', 'eq', 'hash' ])

    def __init__(self, env, factory=None):
        self.env = env
        self.factory = factory
        for meth in self.lazy_methods:
            setattr(self, meth, partial(self.delegated, meth))

    def actualize(self, obj=None):
        if self.factory is None:
            self.obj = obj
        else:
            self.obj = self.factory()
        if self._saved_init is not None:
            self.obj._lazy_init = self._saved_init

    def new_obj(self, cls, *l, **kw):
        if type(cls) == Proxy:
            cls = cls._mgr.obj
        return cls.__new__(cls, *l, **kw)

    def delegated(self, *l, **kw):
        name = l[0]
        l = l[1:]
        if self.actualized:
            obj = self.obj
            init = getattr(obj, '_lazy_init', getattr(obj, '__init__'))
            fun = dict(
                init=init,
                call=obj,
                getattr=partial(getattr, obj),
                setattr=partial(setattr, obj),
                repr=partial(repr, obj),
                str=partial(str, obj),
                iter=partial(iter, obj),
                contains=lambda o:o in obj,
                eq=lambda o:obj==o,
                hash=lambda:hash(obj))[name]
            try:
                result = fun(*l, **kw)
            except TypeError, te:
                log.exception('error in playback')
                import pdb; pdb.set_trace()
                raise
            return result
        else:
            return self.env.log(self.delegated, name, *l, **kw)

    def __repr__(self):
        if self.actualized:
            return '<Actualized LazyObjectManager for %s>' % self.obj
        else:
            return '<Virtualized LazyObjectManager>'
    

class ContextualLazyObjectManager(LazyObjectManager):

    def __init__(self, env, factory=None):
        self.contextfunc = env.contextfunc
        self.registry = {}
        LazyObjectManager.__init__(self, env, factory)

    def _get_actualized(self):
        ctx = self.contextfunc()
        if ctx in self.registry:
            return True
        else:
            return False
    def _set_actualized(self, value):
        pass
    actualized = property(_get_actualized, _set_actualized)

    def _get_obj(self):
        ctx = self.contextfunc()
        return self.registry.get(ctx)
    def _set_obj(self, value):
        ctx = self.contextfunc()
        self.registry[ctx] = value
    obj = property(_get_obj, _set_obj)

class LazyEnviron(object):
    manager_class=LazyObjectManager

    def __init__(self):
        self._root_objects = []
        self._operations = []
        self.actualized = False
        self.frozen = False
        class LazyMeta(type):
            def __new__(meta, name, bases, dct):
                old_init = None
                if self.frozen:
                    new_bases = []
                    for b in bases:
                        om = object_manager(b)
                        if om is None:
                            new_bases.append(b)
                        else:
                            new_bases.append(om.obj)
                    cls = type.__new__(meta, name, tuple(new_bases), dct)
                else:
                    old_init = dct.pop('__init__', None)
                    cls = self.log(meta, name, bases, dct)
                om = object_manager(cls)
                if om is not None and old_init is not None:
                    om.save_init(old_init)
                return cls
        self.LazyMeta = LazyMeta

    @property
    def root_objects(self):
        return self._root_objects

    @property
    def operations(self):
        return self._operations

    def freeze(self):
        self.frozen = True

    def _new_obj(self, mgr):
        assert not self.frozen
        o = Proxy()
        mgr.proxy = o
        o._mgr = mgr
        return o

    def new(self, factory, **kw):
        o = self._new_obj(self.manager_class(self, factory))
        o.__dict__.update(kw)
        self.root_objects.append(o)
        return o

    def log(self, func, *l, **kw):
        result = self._new_obj(self.manager_class(self))
        self.operations.append((result, func, l, kw))
        return result

    def playback(self):
        for i, o in enumerate(self.root_objects):
            o._mgr.actualize()
        for i, (r, f, l, kw) in enumerate(self.operations):
            result = f(*l, **kw)
            r._mgr.actualize(result)

    def clear(self):
        self._root_objects = []
        self._operations = []

class ContextualLazyEnviron(LazyEnviron):
    manager_class=ContextualLazyObjectManager

    def __init__(self, contextfunc):
        LazyEnviron.__init__(self)
        self.contextfunc = contextfunc

